﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using NanoMvvm.Threading;

namespace NanoMvvm
{
    //TODO: test thread-safe
    public class Mediator : IMediator
    {
        private static readonly IMediator Instance = new Mediator();

        private static readonly ReaderWriterLockSlim RegLock = new ReaderWriterLockSlim();

        private readonly IDictionary<RegistrationInfo, IList<object>> registrations = new Dictionary<RegistrationInfo, IList<object>>();

        /// <summary>
        /// Singleton Instance
        /// </summary>
        public static IMediator Default
        {
            get
            {
                return Instance;
            }
        }

        public bool RegisterHandler<TMessage>(Action<TMessage> handler)
        {
            return RegisterHandler(null, handler);
        }

        public bool RegisterHandler<TMessage>(object key, Action<TMessage> handler)
        {
            var regInfo = new RegistrationInfo(typeof(TMessage), key);

            IList<object> regs;
            using (new ReadLock(RegLock))
            {
                if (!registrations.ContainsKey(regInfo))
                {
                    regs = new List<object>();

                    using (new WriteLock(RegLock))
                    {
                        registrations[regInfo] = regs;
                    }
                }
                else
                {
                    regs = registrations[regInfo];

                    if (regs.Cast<IEquatable<Action<TMessage>>>().Any(actionContainer => actionContainer.Equals(handler)))
                    {
                        return false;
                    }
                }
            }

            regs.Add(new WeakActionContainer<TMessage>(handler));

            return true;
        }

        public bool UnregisterHandler<TMessage>(Action<TMessage> handler)
        {
            return UnregisterHandler(null, handler);
        }

        public bool UnregisterHandler<TMessage>(object key, Action<TMessage> handler)
        {
            var regInfo = new RegistrationInfo(typeof(TMessage), key);

            using (new ReadLock(RegLock))
            {
                if (!registrations.ContainsKey(regInfo))
                {
                    return false;
                }

                IList<object> regs = registrations[regInfo];

                var toRemove =
                    regs.Cast<IEquatable<Action<TMessage>>>().Where(actionContainer => actionContainer.Equals(handler)).
                        ToList();

                Debug.Assert(toRemove.Count >= 1, "toRemove.Count >= 1");

                foreach (var o in toRemove)
                {
                    regs.Remove(o);
                }

                if (regs.Count == 0)
                {
                    using (new WriteLock(RegLock))
                    {
                        registrations.Remove(regInfo);
                    }
                }
            }

            return true;
        }

        public int NotifySubscribers<TMessage>(TMessage message)
        {
            return NotifySubscribers(null, message);
        }

        public int NotifySubscribers<TMessage>(object key, TMessage message)
        {
            var regInfo = new RegistrationInfo(typeof(TMessage), key);

            int count;

            using (new ReadOnlyLock(RegLock))
            {
                if (!registrations.ContainsKey(regInfo))
                {
                    return 0;
                }

                IList<object> regs = registrations[regInfo];

                Debug.Assert(regs.Count > 0, "regs.Count > 0");

                count = 0;
                foreach (IWeakExecutable<TMessage> exe in regs)
                {
                    if (!exe.IsTargetAlive)
                    {
                        CleanupDeadReferences();
                        continue;
                    }
                    exe.Execute(message);
                    count++;
                }
            }

            return count;
        }

        public void Clear()
        {
            using (new WriteLock(RegLock))
            {
                registrations.Clear();
            }
        }

        private void CleanupDeadReferences()
        {
            var toRemoveContainer = new List<object>();
            var toRemoveRegistration = new List<RegistrationInfo>();

            foreach (var registration in registrations)
            {
                toRemoveContainer.AddRange(registration.Value.Cast<IWeakContainer>().Where(weakContainer => !weakContainer.IsTargetAlive));

                foreach (var o in toRemoveContainer)
                {
                    registration.Value.Remove(o);
                }

                if (toRemoveContainer.Count == 0) toRemoveRegistration.Add(registration.Key);
            }

            foreach (var info in toRemoveRegistration)
            {
                registrations.Remove(info);
            }
        }

        private struct RegistrationInfo : IEquatable<RegistrationInfo>
        {
            private readonly Type typeKey;

            private readonly object objectKey;

            public RegistrationInfo(Type typeKey, object objectKey)
            {
                this.typeKey = typeKey;
                this.objectKey = objectKey;
            }

            private Type TypeKey
            {
                get { return typeKey; }
            }

            private object ObjectKey
            {
                get { return objectKey; }
            }

            public static bool operator ==(RegistrationInfo r1, RegistrationInfo r2)
            {
                return r1.Equals(r2);
            }

            public static bool operator !=(RegistrationInfo r1, RegistrationInfo r2)
            {
                return !(r1 == r2);
            }

            public bool Equals(RegistrationInfo other)
            {
                return TypeKey == other.TypeKey && Equals(ObjectKey, other.ObjectKey);
            }

            public override bool Equals(object other)
            {
                if (ReferenceEquals(null, other)) return false;

                if (other is RegistrationInfo)
                {
                    return Equals((RegistrationInfo)other);
                }

                return false;
            }

            public override int GetHashCode()
            {
                unchecked // Overflow is fine, just wrap
                {
                    int hash = 17;
                    hash = hash * 23 + (TypeKey == null ? 0 : TypeKey.GetHashCode());
                    hash = hash * 23 + (ObjectKey == null ? 0 : ObjectKey.GetHashCode());
                    return hash;
                }
            }
        }
    }
}